﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Threading;
using ORMAC.Attributes;
using ORMAC.Core;

namespace ORMAC.Reflection
{
    /// <summary>
    /// class to handle internal cache structures
    /// </summary>
    public static class ReflectionCacheManager
    {
        private struct PrimaryKeyInfo
        {
            public string Name;
            public string Schema;
        }
        private class CacheDicdionaries
        {
            /// <summary>
            /// Class constructor
            /// </summary>
            public CacheDicdionaries()
            {
                propertyCustomeAttributes = new Dictionary<Type, Dictionary<string, BaseAttribute[]>>();
                classCustomeAttributes = new Dictionary<Type, BaseAttribute[]>();
                classPrimaryKeys = new Dictionary<Type, PrimaryKeyInfo[]>();
            }
            /// <summary>
            /// list of the properties of each assembly class
            /// </summary>
            public Dictionary<Type, Dictionary<string, BaseAttribute[]>> propertyCustomeAttributes ;
            /// <summary>
            /// list of all class attributes
            /// </summary>
            public Dictionary<Type, BaseAttribute[]> classCustomeAttributes ;
            /// <summary>
            /// list of class primary keys
            /// </summary>
            public Dictionary<Type, PrimaryKeyInfo[]> classPrimaryKeys ;
        }
        private static Dictionary<Type, PropertyInfo[]> classProperties = new Dictionary<Type, PropertyInfo[]>();
        private static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
        private static Dictionary<string, CacheDicdionaries> SchemaDictionaries = new Dictionary<string, CacheDicdionaries>();
        public static IEnumerable<PropertyInfo> GetProperties<T>()
        {
            return GetProperties(typeof(T));
        }

        public static IEnumerable<PropertyInfo> GetProperties(Type classType)
        {
            IEnumerable<PropertyInfo> res = null;
            try
            {
                rwLock.EnterReadLock();
                if (!classProperties.ContainsKey(classType))
                {
                    rwLock.ExitReadLock();
                    AddClassProperties(classType);
                    rwLock.EnterReadLock();
                }
                if (classProperties.ContainsKey(classType))
                {
                    res = classProperties[classType];
                }
            }
            finally
            {
                rwLock.ExitReadLock();
            }
            return res;
        }

        private static void AddClassProperties(Type classType)
        {
            try
            {
                rwLock.EnterWriteLock();
                var properties = classType.GetProperties();
                classProperties.Add(classType, properties);
            }
            finally
            {
                rwLock.ExitWriteLock();
            }
        }
        public static T GetCustomAttribute<T>(string schema, Type classType, string propertyName)
            where T:BaseAttribute
        {
            var attribs=GetCustomAttributes(schema, classType, propertyName, typeof(T));
            if(attribs!=null){
                return attribs.FirstOrDefault() as T;
            }
            return null;
        }
        public static T GetCustomAttribute<T>(string schema, Type classType)
            where T:BaseAttribute
        {
            var attribs = GetCustomAttributes(schema, classType, typeof(T));
            if (attribs != null)
            {
                return attribs.FirstOrDefault() as T;
            }
            return null;
        }
        
        public static IEnumerable<BaseAttribute> GetCustomAttributes<T>(string schema,Type classType, string propertyName)
        {
            return GetCustomAttributes(schema,classType, propertyName, typeof(T));
        }
        public static IEnumerable<BaseAttribute> GetCustomAttributes<T>(string schema,Type classType)
        {
            return GetCustomAttributes(schema,classType,typeof(T));
        }

        private static IEnumerable<BaseAttribute> GetCustomAttributes(string schema, Type classType, Type attributeType)
        {
            IEnumerable<BaseAttribute> res = null;
            try
            {
                CheckAndCreateSchema(schema);
                rwLock.EnterReadLock();
                var classCustomeAttributes = SchemaDictionaries[schema].classCustomeAttributes;
                if (!classCustomeAttributes.ContainsKey(classType))
                {
                    rwLock.ExitReadLock();
                    AddClassCustomAttributes(schema,classType);
                    rwLock.EnterReadLock();
                }
                var attribs = classCustomeAttributes[classType];
                if (attribs!=null)
                    res = attribs.Where(a => a.GetType() == attributeType);
            }
            finally
            {
                rwLock.ExitReadLock();
            }
            return res;
        }

        private static void CheckAndCreateSchema(string schema)
        {
            rwLock.EnterReadLock(); 
            if (!SchemaDictionaries.ContainsKey(schema))
            {
                rwLock.ExitReadLock();
                rwLock.EnterWriteLock();
                SchemaDictionaries.Add(schema, new CacheDicdionaries());
                rwLock.ExitWriteLock();
            }else
                rwLock.ExitReadLock();
        }

        private static void AddClassCustomAttributes(string schema, Type classType)
        {
            try
            {
                rwLock.EnterWriteLock();
                var attribs=classType.GetCustomAttributes(typeof(BaseAttribute),false);
                if (attribs != null)
                {
                    var classCustomeAttributes = SchemaDictionaries[schema].classCustomeAttributes;
                    classCustomeAttributes.Add(classType, attribs.Select<Object, BaseAttribute>(a => (BaseAttribute)a).ToArray());
                }
            }
            finally
            {
                rwLock.ExitWriteLock();
            }
        }
        public static IEnumerable<BaseAttribute> GetCustomAttributes(string schema, Type classType, string propertyName, Type attributeType)
        {
            IEnumerable<BaseAttribute> res = null;
            try
            {
                CheckAndCreateSchema(schema);
                rwLock.EnterReadLock();
                var propertyCustomeAttributes = SchemaDictionaries[schema].propertyCustomeAttributes;
                if (!propertyCustomeAttributes.ContainsKey(classType))
                {
                    rwLock.ExitReadLock();
                    AddClassPropertiesandCustomAttributes(schema,classType);
                    rwLock.EnterReadLock();
                }
                var attribs=propertyCustomeAttributes[classType][propertyName];
                if (attribs != null)
                {
                    res = attribs.Where(a => a.GetType() == attributeType);
                }
            }
            finally
            {
                rwLock.ExitReadLock();
            }
            return res;
        }
        /// <summary>
        /// add class information icluding the properties and primary keys to the chache
        /// </summary>
        /// <param name="schema"></param>
        /// <param name="classType"></param>
        private static void AddClassPropertiesandCustomAttributes(string schema, Type classType)
        {
            try
            {
                rwLock.EnterWriteLock();
                var propertyCustomeAttributes = SchemaDictionaries[schema].propertyCustomeAttributes;
                //check i this class is already added to the cache
                if (propertyCustomeAttributes.ContainsKey(classType))
                    return;
                var classPrimaryKeys = SchemaDictionaries[schema].classPrimaryKeys;
                propertyCustomeAttributes.Add(classType, new Dictionary<string, BaseAttribute[]>());
                var properties=classType.GetProperties();
                if (!classPrimaryKeys.ContainsKey(classType))
                    classPrimaryKeys.Add(classType, new PrimaryKeyInfo[properties.Length]);

                int primaryKeyCount = 0;
                for (int pCount = 0; pCount < properties.Length;pCount++ )
                {   
                    var pInfo = properties[pCount];
                    propertyCustomeAttributes[classType].Add(pInfo.Name, null);
                    var attribs = pInfo.GetCustomAttributes(typeof(BaseAttribute), false);
                    propertyCustomeAttributes[classType][pInfo.Name] = attribs.Select<object,BaseAttribute>(a=> a as BaseAttribute).ToArray<BaseAttribute>();
                    //Add primary keys
                    IEnumerable<BaseAttribute> attribList = propertyCustomeAttributes[classType][pInfo.Name];
                    var primaryKeys = attribList.Where(a => a is PrimaryKeyAttribute);
                    if (primaryKeys != null)
                    {
                        foreach (var primaryKey in primaryKeys)
                        {
                            var table = attribList.FirstOrDefault(a => a is TableAttribute && a.Schema == primaryKey.Schema);
                            var field = attribList.FirstOrDefault(a => a is FieldAttribute && a.Schema == primaryKey.Schema);
                            //JoinColumns join = ReflectionHelper.GetJoinInfo(classType, schema, pInfo.Name);
                            if (table == null)
                            {
                                rwLock.ExitWriteLock();
                                attribList = GetCustomAttributes<TableAttribute>(schema,classType);
                                rwLock.EnterWriteLock();
                                table = attribList.FirstOrDefault(a => a is TableAttribute && a.Schema == primaryKey.Schema);
                            }


                            if (field != null)
                            {
                                string prefix = table != null ? (table as TableAttribute).TableName : classType.Name;
                                classPrimaryKeys[classType][primaryKeyCount++] = new PrimaryKeyInfo
                                {
                                    Name = prefix + "_" + (field as FieldAttribute).FieldName,
                                    Schema = primaryKey.Schema
                                };
                            }
                        }
                    }
                }
                if(primaryKeyCount>0)
                    classPrimaryKeys[classType] = classPrimaryKeys[classType].Take<PrimaryKeyInfo>( primaryKeyCount).ToArray<PrimaryKeyInfo>() ;
                var fields=classType.GetFields();
                foreach (FieldInfo pInfo in fields)
                {
                    var attribs = pInfo.GetCustomAttributes(typeof(BaseAttribute),false);
                    propertyCustomeAttributes[classType].Add(pInfo.Name, null);
                    propertyCustomeAttributes[classType][pInfo.Name] = attribs.Select<object, BaseAttribute>(a => a as BaseAttribute).ToArray<BaseAttribute>();
                }
            }
            finally
            {
                rwLock.ExitWriteLock();
            }
        }

        static public IEnumerable<string> GetPrimaryKeys(Type classType, string schema)
        {
            IEnumerable<string> res=null;
            rwLock.EnterReadLock();
            var classPrimaryKeys = SchemaDictionaries[schema].classPrimaryKeys;
            var attribs = classPrimaryKeys[classType];
            if (attribs!=null)
            {
                var ires = attribs.Where(p=>p.Schema==schema);
                if(ires!=null)
                    res=ires.Select<PrimaryKeyInfo,string>(i=>i.Name);
            }
            rwLock.ExitReadLock();
            return res;
        }

    }
}
